/**
 * 
 */
package qy.jalgotrade.technical.ma;

import java.time.ZonedDateTime;

import com.google.common.math.DoubleMath;

import it.unimi.dsi.fastutil.doubles.DoubleList;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.technical.EventBasedFilter;
import qy.jalgotrade.technical.EventWindow;
import qy.jalgotrade.utils.CommonUtils;

/**
 * @author qy
 *
 */
public class KAMA extends EventBasedFilter<Double> {

	protected class KamaEventWindow extends EventWindow<Double> {

		private int __fastPeriod;

		private int __slowPeriod;

		private double __value;

		public KamaEventWindow(int windowSize, int fastPeriod, int slowPeriod) {

			super(windowSize, Double.class);
			assert fastPeriod > 0 && slowPeriod > fastPeriod;
			__fastPeriod = fastPeriod;
			__slowPeriod = slowPeriod;
			__value = Double.NaN;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * qy.jalgotrade.technical.EventBasedFilter.EventWindow#onNewValue(java.time.
		 * ZonedDateTime, java.lang.Object)
		 */
		@Override
		public void onNewValue(ZonedDateTime dateTime, Object value) {

			super.onNewValue(dateTime, value);

			if (value != null && windowFull()) {
				if (Double.isNaN(__value)) {

					__value = (double) value;
				} else {
					DoubleList values = (DoubleList) getValues();
					if (values.size() > 0) {
						// calc ER & SC: ER = sum(Volatitily) / change
						double di = values.getDouble(values.size() - 1) - values.getDouble(0);
						double vola = 0;
						double er = Double.NaN;
						for (int i = 0; i < values.size(); i++) {
							if (i - 1 < 0) {
								vola += 0;
							} else {
								vola += Math.abs(values.getDouble(i) - values.getDouble(i - 1));
							}
						}
						er = !DoubleMath.fuzzyEquals(vola, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)
								? Math.abs(di / vola)
								: Double.NaN;
						// SC = (er * (fastSc - slowSc) + slowSc) ^ 2
						double sc = Math.pow(
								er * (2.0 / (__fastPeriod + 1) - 2.0 / (__slowPeriod + 1)) + 2.0 / (__slowPeriod + 1), 2);
						// KAMA = KAMA(-1) + SC * (price - KAMA(-1))
						__value = __value + sc * ((double) value - __value);
					}
				}
			}
		}

		@Override
		public Double getValue() {

			return __value;
		}
	}

	/**
	 * 
	 * @param  dataSeries
	 * @param  windowSize
	 * @param  fastPeriod
	 * @param  slowPeriod
	 * @throws Exception
	 */
	public KAMA(SequenceDataSeries<Double> dataSeries, int windowSize, int fastPeriod, int slowPeriod) throws Exception {

		this(dataSeries, windowSize, fastPeriod, slowPeriod, 0);
	}

	/**
	 * 
	 * @param  dataSeries
	 * @param  windowSize
	 * @param  fastPeriod
	 * @param  slowPeriod
	 * @param  maxLen
	 * @throws Exception
	 */
	public KAMA(SequenceDataSeries<Double> dataSeries, int windowSize, int fastPeriod, int slowPeriod, int maxLen)
			throws Exception {

		super(maxLen);
		KamaEventWindow win = new KamaEventWindow(windowSize, fastPeriod, slowPeriod);
		_init(dataSeries, win);
	}
}
